package alioss

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"gitee.com/851537/gbase"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"github.com/aliyun/credentials-go/credentials"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/emirpasic/gods/maps/treemap"
	jsoniter "github.com/json-iterator/go"
	"hash"
	"io"
	"time"
)

var (
	clients *treemap.Map = treemap.NewWithStringComparator() //k:accId.bucketName
)

// Access Key ID（访问密钥ID）
// Access Key Secret（访问密钥Secret）
// Bucket Name（存储空间名称）xx
// Endpoint（VPC）：oss-cn-nanjing-internal.aliyuncs.com
// Bucket 域名：xx.oss-cn-nanjing-internal.aliyuncs.com
type Client struct {
	AccId      string
	AccSec     string
	BucketName string //eg. xxx(创建桶时的名称)
	EndPoint   string //eg. oss-cn-hangzhou.aliyuncs.com
	Region     string //eg. cn-nanjing(与endPoint中的一致)
	Oss        *oss.Client

	//前端直传临时token
	//PolicyToken struct {
	//	Token      *PolicyToken
	//	ExpireTime int64
	//}
}

func (this *Client) GetBucketDomain() string {
	return fmt.Sprintf("https://%s.%s", this.BucketName, gbase.Url(this.EndPoint).ToUrl().Host)
}

func (this *Client) GetFileUrl(filename string) string {
	return fmt.Sprintf("%s/%s", this.GetBucketDomain(), filename)
}

func (this *Client) GetBucket() (bucket *oss.Bucket, err error) {
	return this.Oss.Bucket(this.BucketName)
}

// remoteFile为存放到存储桶的位置及文件名，目前好像只能放到根目录下，保持唯一，同名会覆盖 eg. uniKey.png
// 返回的uniFilename为唯一文件名
func (this *Client) PutObject(remoteFile string, reader io.Reader) (err error) {
	bucket, err := this.Oss.Bucket(this.BucketName)
	if err != nil {
		return
	}
	err = bucket.PutObject(remoteFile, reader)
	return
}

// localFile:本地文件路径，必须为长相对路径
// remoteFile:存储桶上的文件，保持唯一，同名会覆盖 eg. uniKey.png
// 返回的uniFilename为唯一文件名
func (this *Client) PutObjectFromFile(localFile gbase.Filepath, remoteFile string) (err error) {
	bucket, err := this.Oss.Bucket(this.BucketName)
	if err != nil {
		return
	}
	//shortPath := filepath.UnwrapLong()
	//uniFilename = fmt.Sprintf("%s%s", uuid.NewString(), filepath.GetFileExt())
	err = bucket.PutObjectFromFile(remoteFile, localFile.ToString())
	return
}

func (this *Client) PutObjectFromUrl(url gbase.Url, remoteFile string) (err error) {
	data, err := url.GetBytes()
	if err != nil {
		return err
	}

	reader := bytes.NewReader(data)
	err = ClientInst().PutObject(remoteFile, reader)
	return
}

// file为系统短路径,eg. /xx/xx.png
func (this *Client) GetObject(file gbase.Filepath) (data []byte, err error) {
	bucket, err := this.GetBucket()
	if err != nil {
		return nil, err
	}
	// 下载文件到流。
	body, err := bucket.GetObject(file.Md5())
	if err != nil {
		return nil, err
	}
	// 数据读取完成后，获取的流必须关闭，否则会造成连接泄漏，导致请求无连接可用，程序无法正常工作。
	defer body.Close()
	return io.ReadAll(body)
}

func (this *Client) GetObjectToFile(file gbase.Filepath) (err error) {
	bucket, err := this.GetBucket()
	if err != nil {
		return err
	}
	return bucket.GetObjectToFile(file.Md5(), file.WrapLong().ToString())
}

// 获取前端直传文件到oss的token,RAM用户获取STS临时访问凭证计算签名 V4签名计算
func (this *Client) GetRamStsPolicyToken(roleArn string) (token *PolicyToken, err error) {
	// 设置bucket所处地域
	//region = "cn-hangzhou"
	// 设置bucket名称
	//bucketName = "bucketName"
	product := "oss"
	config := new(credentials.Config).
		// 填写Credential类型，固定值为ram_role_arn。
		SetType("ram_role_arn").
		// 从环境变量中获取RAM用户的访问密钥（AccessKeyId和AccessKeySecret）。
		SetAccessKeyId(this.AccId).
		SetAccessKeySecret(this.AccSec).
		// 从环境变量中获取RAM角色的ARN信息，即需要扮演的角色ID。格式为acs:ram::$accountID:role/$roleName。
		SetRoleArn(roleArn).
		// 自定义角色会话名称，用于区分不同的令牌。
		SetRoleSessionName("dyt").
		// （可选）限制STS Token权限。
		SetPolicy("").
		// （可选）限制STS Token的有效时间。
		SetRoleSessionExpiration(3600)

	// 根据配置创建凭证提供器
	provider, err := credentials.NewCredential(config)
	if err != nil {
		hlog.Debugf("NewCredential fail, err:%v", err)
		return
	}

	// 从凭证提供器获取凭证
	cred, err := provider.GetCredential()
	if err != nil {
		hlog.Debugf("GetCredential fail, err:%v", err)
		return
	}
	// 构建policy
	utcTime := time.Now().UTC()
	date := utcTime.Format("20060102")
	// 设置签名过期时间为当前时间起一个小时后过期
	expiration := utcTime.Add(12 * time.Hour)
	// 示例policy表单域只列举了部分必填字段，如有其他需求可参考文档：https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend
	policyMap := map[string]any{
		"expiration": expiration.Format("2006-01-02T15:04:05.000Z"),
		"conditions": []any{
			map[string]string{"bucket": this.BucketName},
			map[string]string{"x-oss-signature-version": "OSS4-HMAC-SHA256"},
			map[string]string{"x-oss-credential": fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request", *cred.AccessKeyId, date, this.Region, product)},
			map[string]string{"x-oss-date": utcTime.Format("20060102T150405Z")},
			map[string]string{"x-oss-security-token": *cred.SecurityToken},
		},
	}

	// 将policy转换为 JSON 格式
	policy, err := jsoniter.Marshal(policyMap)
	if err != nil {
		hlog.Debugf("GetRamStsPolicyToken fail, err:%v", err)
		return
	}

	// 构造待签名字符串（StringToSign）
	stringToSign := base64.StdEncoding.EncodeToString([]byte(policy))

	hmacHash := func() hash.Hash { return sha256.New() }
	// 构建signing key
	signingKey := "aliyun_v4" + *cred.AccessKeySecret
	h1 := hmac.New(hmacHash, []byte(signingKey))
	io.WriteString(h1, date)
	h1Key := h1.Sum(nil)

	h2 := hmac.New(hmacHash, h1Key)
	io.WriteString(h2, this.Region)
	h2Key := h2.Sum(nil)

	h3 := hmac.New(hmacHash, h2Key)
	io.WriteString(h3, product)
	h3Key := h3.Sum(nil)

	h4 := hmac.New(hmacHash, h3Key)
	io.WriteString(h4, "aliyun_v4_request")
	h4Key := h4.Sum(nil)

	// 生成签名
	h := hmac.New(hmacHash, h4Key)
	io.WriteString(h, stringToSign)
	signature := hex.EncodeToString(h.Sum(nil))

	// 构建返回给前端的表单
	token = &PolicyToken{
		Policy: stringToSign,
		Credential: fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request",
			*cred.AccessKeyId, date, this.Region, product),
		SignatureVersion: "OSS4-HMAC-SHA256",
		Signature:        signature,
		Date:             utcTime.UTC().Format("20060102T150405Z"),
		SecurityToken:    *cred.SecurityToken,
	}
	return
	//response, err := jsoniter.MarshalToString(policyToken)
	//if err != nil {
	//	hlog.Debugf("GetRamStsPolicyToken MarshalToString fail, err:%v", err)
	//	return
	//}
	////fmt.Println(string(response))
	//// 返回表单
	//return response, nil
}

func NewClient(endPoint, accId, accSec, bucketName string) (client *Client, err error) {
	client = &Client{}
	client.Oss, err = oss.New(endPoint, accId, accSec)
	if err != nil {
		return nil, err
	}
	client.AccId = accId
	client.AccSec = accSec
	client.EndPoint = endPoint
	client.BucketName = bucketName
	//client.Region = region
	clients.Put(fmt.Sprintf("%s.%s", accId, bucketName), client)
	return
}

// accId不传，默认获取第一个
func ClientInst(accId ...string) *Client {

	if clients.Size() == 0 {
		panic("该阿里云OSS客户端未初始化01")
	}

	if len(accId) > 0 {
		if v, ok := clients.Get(accId[0]); !ok {
			panic("该阿里云OSS客户端未初始化02")
		} else {
			return v.(*Client)
		}
	} else {
		_, v := clients.Min()
		return v.(*Client)
	}
}
